﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;


namespace advadev.DataStorage
{

    /// <summary>
    /// Represents a generic entity xml file data storage
    /// </summary>
    public class GenericEntityXmlFileDataStorage : IGenericEntityDataStorage
    {

        /// <summary>
        /// Initializes a new instance of the GenericEntityXmlFileDataStorage
        /// class by the given parameter
        /// </summary>
        /// <param name="fileName">
        /// A file name string that references the file
        /// </param>
        /// <exception cref="ArgumentException">
        /// Will be thrown if the file name is not valid
        /// </exception>
        public GenericEntityXmlFileDataStorage(string fileName)
        {
            try
            {
                if (!File.Exists(fileName))
                {
                    using (FileStream fileStream = File.Open(fileName, FileMode.CreateNew))
                    {
                        using (XmlWriter writer = XmlWriter.Create(fileStream))
                        {
                            writer.WriteStartDocument(true);
                            writer.WriteWhitespace(Environment.NewLine);
                            writer.WriteStartElement("GenericEntities");
                            writer.WriteEndElement();
                            writer.Flush();
                        }
                    }
                }
                FileName = fileName;
                Document = XDocument.Load(fileName);
                var genericEntitiesNodes = from c in Document.Descendants()
                                           where c.Name.LocalName == "GenericEntities"
                                           select c;
                foreach (var genericEntitiesNode in genericEntitiesNodes)
                {
                    GenericEntitiesNode = genericEntitiesNode;
                    break;
                }
            }
            catch (Exception e)
            {
                throw new ArgumentException(e.Message, "fileName");
            }
        }


        /// <summary>
        /// Gets or sets the file name
        /// </summary>
        private string FileName
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the document
        /// </summary>
        private XDocument Document
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the generic entities node
        /// </summary>
        private XElement GenericEntitiesNode
        {
            get;
            set;
        }


        #region IGenericEntityDataStorage implementation
        /// <summary>
        /// Saves the given generic entity
        /// </summary>
        /// <param name="genericEntity">
        /// Generic entity to save
        /// </param>
        public void Save(GenericEntity genericEntity)
        {
            if (genericEntity == null)
            {
                return;
            }
            XElement existingItem = findExisting(genericEntity.EntityId, genericEntity.ApplicationKey, genericEntity.FullClassName);
            XElement newItem = new XElement(
                "GenericEntity",
                new XAttribute("EntityId", genericEntity.EntityId),
                new XAttribute("ApplicationKey", genericEntity.ApplicationKey),
                new XAttribute("FullClassName", genericEntity.FullClassName),
                genericEntity.XmlSerialization);
            if (existingItem == null)
            {
                // -> insert
                GenericEntitiesNode.Add(newItem);
            }
            else
            {
                // -> update
                existingItem.ReplaceWith(newItem);
            }
            Document.Save(FileName);
        }

        /// <summary>
        /// Saves the given list of generic entities
        /// </summary>
        /// <param name="genericEntities">
        /// List of generic entities to save
        /// </param>
        public void Save(IList<GenericEntity> genericEntities)
        {
            if (genericEntities == null || genericEntities.Count == 0)
            {
                return;
            }
            foreach (GenericEntity genericEntity in genericEntities)
            {
                XElement existingItem = findExisting(genericEntity.EntityId, genericEntity.ApplicationKey, genericEntity.FullClassName);
                XElement newItem = new XElement(
                    "GenericEntity",
                    new XAttribute("EntityId", genericEntity.EntityId),
                    new XAttribute("ApplicationKey", genericEntity.ApplicationKey),
                    new XAttribute("FullClassName", genericEntity.FullClassName),
                    genericEntity.XmlSerialization);
                if (existingItem == null)
                {
                    // -> insert
                    GenericEntitiesNode.Add(newItem);
                }
                else
                {
                    // -> update
                    existingItem.ReplaceWith(newItem);
                }
            }
            Document.Save(FileName);
        }

        /// <summary>
        /// Finds the generic entity with the given id, application key and type
        /// </summary>
        /// <param name="id">
        /// Id of the generic entity
        /// </param>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <param name="fullClassName">
        /// Full class name of the type
        /// </param>
        /// <returns>
        /// The generic entity or null
        /// </returns>
        public GenericEntity Find(string id, string applicationKey, string fullClassName)
        {
            if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(applicationKey) || string.IsNullOrEmpty(fullClassName))
            {
                return null;
            }
            var existingItems = from c in GenericEntitiesNode.Descendants("GenericEntity")
                                where c.Attribute("EntityId").Value == id &&
                                c.Attribute("ApplicationKey").Value == applicationKey &&
                                c.Attribute("FullClassName").Value == fullClassName
                                select new
                                {
                                    EntityId = c.Attribute("EntityId"),
                                    ApplicationKey = c.Attribute("ApplicationKey"),
                                    FullClassName = c.Attribute("FullClassName"),
                                    XmlSerialization = c.FirstNode
                                };
            foreach (var existingItem in existingItems)
            {
                if (existingItem != null && existingItem.ApplicationKey != null && existingItem.FullClassName != null && existingItem.EntityId != null && existingItem.XmlSerialization != null)
                {
                    GenericEntity genericEntity = new GenericEntity();
                    genericEntity.ApplicationKey = existingItem.ApplicationKey.Value;
                    genericEntity.FullClassName = existingItem.FullClassName.Value;
                    genericEntity.EntityId = existingItem.EntityId.Value;
                    genericEntity.XmlSerialization = (existingItem.XmlSerialization as XElement);
                    return genericEntity;
                }
            }
            return null;
        }

        /// <summary>
        /// Selects all generic entities with the given application key and type
        /// </summary>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <param name="fullClassName">
        /// Full class name of the type
        /// </param>
        /// <returns>
        /// A list of generic entities
        /// </returns>
        public IList<GenericEntity> Select(string applicationKey, string fullClassName)
        {
            IList<GenericEntity> result = new List<GenericEntity>();
            if (string.IsNullOrEmpty(applicationKey) || string.IsNullOrEmpty(fullClassName))
            {
                return result;
            }
            var existingItems = from c in GenericEntitiesNode.Descendants("GenericEntity")
                                where c.Attribute("ApplicationKey").Value == applicationKey &&
                                c.Attribute("FullClassName").Value == fullClassName
                                select new
                                {
                                    EntityId = c.Attribute("EntityId"),
                                    ApplicationKey = c.Attribute("ApplicationKey"),
                                    FullClassName = c.Attribute("FullClassName"),
                                    XmlSerialization = c.FirstNode
                                };
            foreach (var existingItem in existingItems)
            {
                if (existingItem != null && existingItem.ApplicationKey != null && existingItem.FullClassName != null && existingItem.EntityId != null && existingItem.XmlSerialization != null)
                {
                    GenericEntity genericEntity = new GenericEntity();
                    genericEntity.ApplicationKey = existingItem.ApplicationKey.Value;
                    genericEntity.FullClassName = existingItem.FullClassName.Value;
                    genericEntity.EntityId = existingItem.EntityId.Value;
                    genericEntity.XmlSerialization = (existingItem.XmlSerialization as XElement);
                    result.Add(genericEntity);
                }
            }
            return result;
        }

        /// <summary>
        /// Deletes the given generic entity
        /// </summary>
        /// <param name="genericEntity">
        /// Generic entity to delete
        /// </param>
        public void Delete(GenericEntity genericEntity)
        {
            if (genericEntity == null)
            {
                return;
            }
            XElement existingItem = findExisting(genericEntity.EntityId, genericEntity.ApplicationKey, genericEntity.FullClassName);
            if (existingItem != null)
            {
                existingItem.Remove();
            }
            Document.Save(FileName);
        }

        /// <summary>
        /// Deletes the given list of generic entities
        /// </summary>
        /// <param name="genericEntities">
        /// List of generic entities to delete
        /// </param>
        public void Delete(IList<GenericEntity> genericEntities)
        {
            if (genericEntities == null || genericEntities.Count == 0)
            {
                return;
            }
            foreach (GenericEntity genericEntity in genericEntities)
            {
                if (genericEntity != null)
                {
                    XElement existingItem = findExisting(genericEntity.EntityId, genericEntity.ApplicationKey, genericEntity.FullClassName);
                    if (existingItem != null)
                    {
                        existingItem.Remove();
                    }
                }
            }
            Document.Save(FileName);
        }

        /// <summary>
        /// Deletes all generic entities with the given application key and type
        /// </summary>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <param name="fullClassName">
        /// Full class name of the type
        /// </param>
        public void Delete(string applicationKey, string fullClassName)
        {
            if (string.IsNullOrEmpty(applicationKey) || string.IsNullOrEmpty(fullClassName))
            {
                return;
            }
            Collection<XElement> removeElements = new Collection<XElement>();
            var existingItems = from c in GenericEntitiesNode.Descendants("GenericEntity")
                                where c.Attribute("ApplicationKey").Value == applicationKey &&
                                c.Attribute("FullClassName").Value == fullClassName
                                select c;
            foreach (var existingItem in existingItems)
            {
                if (existingItem != null)
                {
                    removeElements.Add(existingItem);
                }
            }
            foreach (XElement removeElement in removeElements)
            {
                removeElement.Remove();
            }
            Document.Save(FileName);
        }

        /// <summary>
        /// Deletes all generic entities with the given application key
        /// </summary>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        public void Delete(string applicationKey)
        {
            if (string.IsNullOrEmpty(applicationKey))
            {
                return;
            }
            Collection<XElement> removeElements = new Collection<XElement>();
            var existingItems = from c in GenericEntitiesNode.Descendants("GenericEntity")
                                where c.Attribute("ApplicationKey").Value == applicationKey
                                select c;
            foreach (var existingItem in existingItems)
            {
                if (existingItem != null)
                {
                    removeElements.Add(existingItem);
                }
            }
            foreach (XElement removeElement in removeElements)
            {
                removeElement.Remove();
            }
            Document.Save(FileName);
        }

        /// <summary>
        /// Finds all full class names with the given application key
        /// </summary>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <returns>
        /// List of full class names or empty list
        /// </returns>
        public IList<string> FindFullClassNames(string applicationKey)
        {
            IList<string> result = new List<string>();
            if (string.IsNullOrEmpty(applicationKey))
            {
                return result;
            }
            var fullClassNames = from c in GenericEntitiesNode.Descendants("GenericEntity")
                                 where c.Attribute("ApplicationKey").Value == applicationKey
                                 select c.Attribute("FullClassName").Value;
            foreach (var fullClassName in fullClassNames)
            {
                if (fullClassName != null)
                {
                    if (!result.Contains(fullClassName))
                    {
                        result.Add(fullClassName);
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Finds all application keys
        /// </summary>
        /// <returns>
        /// List of application keys or empty list
        /// </returns>
        public IList<string> FindApplicationKeys()
        {
            IList<string> result = new List<string>();
            var applicationKeys = from c in GenericEntitiesNode.Descendants("GenericEntity")
                                  select c.Attribute("ApplicationKey").Value;
            foreach (var applicationKey in applicationKeys)
            {
                if (applicationKey != null)
                {
                    if (!result.Contains(applicationKey))
                    {
                        result.Add(applicationKey);
                    }
                }
            }
            return result;
        }
        #endregion IGenericEntityDataStorage implementation


        #region helpers
        /// <summary>
        /// Finds a existing generic database entity
        /// </summary>
        /// <param name="entityId">
        /// Entity id
        /// </param>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <param name="fullClassName">
        /// Full class name
        /// </param>
        /// <returns>
        /// Generic database entity or null
        /// </returns>
        private XElement findExisting(string entityId, string applicationKey, string fullClassName)
        {
            var existingItems = from c in GenericEntitiesNode.Descendants()
                                where c.Name.LocalName == "GenericEntity" &&
                                c.Attribute("EntityId").Value == entityId &&
                                c.Attribute("ApplicationKey").Value == applicationKey &&
                                c.Attribute("FullClassName").Value == fullClassName
                                select c;
            foreach (XElement existingItem in existingItems)
            {
                return existingItem;
            }
            return null;
        }
        #endregion helpers

    }

}
